其實我只是想做一個可以惡搞 Events 執行順序的 plugin 而已(攤手)。
噗浪討論串 1
噗浪討論串 2
Plugin 先奉上,
(function($) {
$.fn.superbind = function(order, type, data, fn) {
if ( $.isFunction( data ) ) {
fn = data;
data = undefined;
}
var order = (typeof order !== "number" || order < 0) ? 0 : order;
return this.each(function() {
/* TODO: fire event before inline event.
if ( order === 0 && typeof $(this).attr("on"+type) === "function") {
}
*/
var _ = setTimeout( function() {
var guid = 0;
$.each( $.cache, function() {
if ( this.events && this.events[ "live" ] && this.events[ type ] ) {
var len = this.events[ type ].length - 1;
guid = parseInt(this.events[ "live" ][ len ].guid) + 1;
}
});
$.each( $.cache, function() {
var reorder = [], events_len = 0, reorder_len = 0;
var obj = {
data: data,
guid: guid,
handler: fn,
namespace: "",
type: type
};
if ( this.events && this.events[ type ] && !this.events[ "live" ] ) {
events_len = this.events[ type ].length;
order = (order > events_len) ? events_len : order;
for(var i in this.events[ type ]) {
if (order === events_len && parseInt(i) === events_len-1) {
reorder.push(this.events[ type ][i]);
reorder.push(obj);
order = -1;
} else if ( parseInt(i) === order || order === 0) {
reorder.push(obj);
reorder.push(this.events[ type ][i]);
order = -1;
} else {
reorder.push(this.events[ type ][i]);
}
}
this.events[ type ] = reorder;
reorder = [];
delete reorder;
}
});
}, 20);
return this;
});
};
})(jQuery);
最近為了一些應用上的效果,所以特別研究了一下 Events 的資料。挖開 jQuery 之後才發現,這是一個很奇妙的世界(疑)。結合各家神人討論,所以我來心得報告一下(喂)。特別感謝大澤木小鐵、費拉諾蘭大公鼎力相助(一拜)。
通常在 jQuery 裡面,我們使用 Events 的作法,有三種:
// 1. 使用 bind 來綁定 event
$("#elem").bind("click", function(e) { ... });
// 2. 直接使用 event
$("#elem").click(function(e) { ... });
// 3. 使用 live 來綁定 event
$("#elem").live("click", function(e) { ... });
其實在 DOM 中還有一種,則是:
<!-- 4. inline 的寫法 -->
<div id="elem" onclick=" ... "> ... </div>
總共有四種方法,我們可以使用 Events。然而,這四種使用 Event 的優先順序,分別是:
4 > 3 = 2 > 1
疑?不要問我為什麼,因為實驗的結果就是這樣(喂)。當我們把 jQuery 三種使用 Events 的資料傾印出來的時候,會發現一件很神奇的事情,使用 bind/click 直接綁定的 Events,其資料是儲存於元件本身,而使用 live 綁定時,資料會儲存於 document 裡面。再根據費拉諾蘭大公所述,三者的資料都會儲存於 $.cache 裡面(使用陣列儲存)。
所以,我們分別來看這些事情:
<!doctype html>
<html lang="en" class="no-js">
<meta charset="utf-8">
<style>
#main {
width: 400px;
height: 600px;
border: 1px solid #000000;
color: #ff3333;
}
</style>
<div id="container">
<div id="main" onclick="console.log(4);"></div>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script>!window.jQuery && document.write('<script src="./js/jquery-1.4.2.min.js"><\/script>')</script>
<script>
$(document).ready(function() {
$("#main").bind("click", function(e) {
console.log(3);
console.log($.data(this).events);
});
$("#main").click(function(e) {
console.log(2);
console.log($.data(this).events);
});
$("#main").live("click", function(e) {
console.log(1);
console.log($.data(this).events);
console.log($.data(document).events);
console.log("$.cache");
console.log($.cache);
});
});
</script>
以上的例子請使用 Chrome 或是 Firefox with Firebug 開啟,資訊會記錄在 console 底下。
依照 $.cache 的儲存方法,他會將元素上的 click/bind 跟 live 分開存放,但是請注意,他並不會儲存 DOM 裡 inline 寫法的 onclick,所以,並不能單純使用這個方法去判斷是否有該 Events 存在或是執行。此外,因為 $.cache 是以陣列的方式儲存這些事情,而且他也並不能直覺的取得所綁定的對象,所以在資訊取出上,有相對麻煩的地方。
當你將 $.cache 列出來時,你會發現 live 這個方法,會產生一個相同的 event 方法,換句話說,你使用 bind/click 去綁定一個 Event 時,他只會有一個 event 方法,而,倘若你使用 live 去綁定一個 Event 時,他同時會衍生出一個該 Event 的 event 方法(超饒舌)。
// 這一段 Javascript 可以加在上述 live 的 function 底下
console.log("======================");
for(i in $.cache) {
console.log("這是第 "+i.toString()+" 個 Event cache。");
var events = $.cache[i].events || null;
if(typeof events === "object") {
for(event in events) {
console.log("這是使用 "+event.toString()+" 綁定。");
console.log(events[event]);
if(events[event].length > 1) {
for(j in events[event]) {
console.log("這是第 "+(parseInt(j)+1).toString()+" 個 Event 綁定。");
console.log(events[event][j]);
}
}
}
}
}
上述例子,點了方框之後,他會將 $.cache 的資訊列出來給你看。你會發現 live 會多一份 Event 的綁定,至於為什麼?因為他是 live 啊(喂)!
這裡有一個不小心實驗出來的問題,當你使用 bind/click 時,倘若在 handler function 中使用了 return false; 的話,那麼 live 所綁定的 Event 會略過不執行。原因是,因為 live 把 Event 偷偷地綁在 document 上面,由於 DOM 中 addEventListener 的規則,你使用 return false 就等同於 stopPropagation() 的意思(其實 return false 也會連帶執行 preventDefault())。
所以,在 document 的子元件執行 stopPropagation(),想當然爾,document 所綁定的 Events 就不會被執行了,這一點可得小心為上。